package ga_yad2;

import genetic_algorithm.Chromosome;
import genetic_algorithm.FitnessFunction;
import genetic_algorithm.Mutation;

import java.util.AbstractMap;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

import utils.RandomGenerator;

public class Yad2Mutation implements Mutation {

	public static final int EXHAUSTIVE = 0;
	public static final int LOCAL = 1;
	
	private FitnessFunction fitnessFunction; // used to measure chromosome's fitness
	private int mode; // mode for mutation
	
	public Yad2Mutation(FitnessFunction fitnessFunction, int mode) {
		this.fitnessFunction = fitnessFunction;
		this.mode = mode;
	}
	
	/**
	 * Mutates each chromosome (in chance of mutation rate)
	 * by swapping a random product of the two most unsatisfied persons
	 */
	@Override
	public void mutate(List<Chromosome> chromosomes, double rate) {

		for(Chromosome c : chromosomes)
		{
			if (RandomGenerator.nextDouble() < rate) {

				// mutate chromosome according to specified mode
				switch (mode) {
				case EXHAUSTIVE: {
					
					exhaustiveMutation(c);
					break;
				}
				case LOCAL: {
					
					localMutation(c);
					break;
				}

				default:
					break;
				}
			}
		}
	}

	private void localMutation(Chromosome chromosome) {

		// sort persons by their satisfaction (ascending order)
		List<Entry<Integer,Integer>> scores = scorePersons(chromosome);
		
		// get two most unsatisfied persons
		int firstPerson = scores.get(0).getKey();
		int secondPerson = scores.get(1).getKey();
		
		// choose a random product for two most unsatisfied persons
		int firstProductIndex = chooseRandProd(firstPerson);
		int secondProductIndex = chooseRandProd(secondPerson);
		
		// swap these products
		swapProducts(firstPerson, firstProductIndex, secondPerson, secondProductIndex, chromosome);
	}
	
	private List<Entry<Integer,Integer>> scorePersons(Chromosome chromosome) {

		// initialize return value
		List<Entry<Integer,Integer>> scores = new LinkedList<Entry<Integer,Integer>>();
		
		// calculate satisfaction for each person
		int numPersons = Yad2Chromosome.productsSplit.size();		
		for (int currPerson = 0 ; currPerson < numPersons ; ++currPerson) {
			scores.add(new AbstractMap.SimpleEntry<Integer,Integer>(currPerson, Yad2Fitness.getFitnessOfOnePerson(chromosome, currPerson)));
		}

		// sort list in ascending order of satisfaction
		Collections.sort(scores, new Comparator<Entry<Integer, Integer>>() {
			public int compare(Entry<Integer, Integer> first,
					Entry<Integer, Integer> second) {
				return first.getValue().compareTo(second.getValue());
			}
		});
		
		// return score list
		return scores;
	}

	private void exhaustiveMutation(Chromosome chromosome) {
		
		// initialize data to keep track of pairs to swap
		double bestFitness = fitnessFunction.getFitness(chromosome); // best fitness achieved by any swap (must be better than current)
		int firstSwapIndex = -1; // index of product to swap of first persons
		int secondSwapIndex = -1; // index of product to swap of second persons
		int firstPerson = -1; // first person index
		int secondPerson = -1; // second person index
		
		// check all possible pairs
		int numPersons = Yad2Chromosome.productsSplit.size();
		for (int i = 0 ; i < numPersons ; ++i) {
			for (int j = i + 1 ; j < numPersons ; ++j) {
				
				// choose random product to swap between current pair
				int firstProductIndex = chooseRandProd(i);
				int secondProductIndex = chooseRandProd(j);
				
				// create a working copy of given chromosome
				Chromosome chromCopy = new Yad2Chromosome(chromosome.getAllValues());
				
				// swap the products
				swapProducts(i, firstProductIndex, j, secondProductIndex, chromCopy);
								
				// measure new fitness
				double fitness = fitnessFunction.getFitness(chromCopy);
				
				// update best swap's data
				if (bestFitness < fitness) {
					
					bestFitness = fitness;
					firstSwapIndex = firstProductIndex;
					secondSwapIndex = secondProductIndex;
					firstPerson = i;
					secondPerson = j;
				}
			}
		}
		
		// if found a swap to improve chromosome, perform it
		if (firstPerson != -1) {
			
			// make best swap
			swapProducts(firstPerson, firstSwapIndex, secondPerson, secondSwapIndex, chromosome);
		}
	}
	
	private int chooseRandProd(int person) {
		return Yad2Chromosome.productsSplit.get(person)[Yad2Chromosome.START]
				+ RandomGenerator.nextInt(Yad2Chromosome.productsSplit
						.get(person)[Yad2Chromosome.AMOUNT]);
	}
	
	private void swapProducts(int firstPerson, int firstProductIndex,
			int secondPerson, int secondProductIndex, Chromosome chromosome) {

		// get the specified products
		Character firstProduct = (Character) chromosome.getValue(firstProductIndex);
		Character secondProduct = (Character) chromosome.getValue(secondProductIndex);
		
		// swap these products
		chromosome.setValue(firstProductIndex, secondProduct);
		chromosome.setValue(secondProductIndex, firstProduct);
	}
}
